import random
from prettytable import *


class DirectionError(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return "未知的方向：{direction}".format(direction=self.value)


class MapEdgeError(Exception):
    def __init__(self, pos) -> None:
        self.pos = pos

    def __str__(self) -> str:
        return "已抵达地图边界{pos}".format(pos=self.pos)


class Place:
    def __init__(self, name=None, info=None):
        self.name = name  # 地点名称
        self.info = f"    {info}"  # 地点信息（加4个空格）
        self.units = []  # 地点内单位
        self.players = []  # 地点内玩家
        self.enterable = True  # 是否可进入，默认为真
        self.portal = False  # 是否可传送至其他地点，默认为否

    def __str__(self):
        return self.name

    def setName(self, name: str) -> None:
        '''
        设置名字（虽然大多数时候用不上
        '''
        self.name = name

    def setInfo(self, info: str) -> None:
        '''
        设置描述（一大段的那种
        '''
        self.info = info

    def addUnit(self, unit) -> None:
        '''
        添加场景内单位
        '''
        self.units.append(unit)

    def addUnits(self, units: list) -> None:
        for unit in units:
            self.units.append(unit)

    def getName(self) -> str | None:
        return self.name

    def getInfo(self) -> str | None:
        return self.info

    def getUnits(self) -> list:
        return self.units

    def getEnterable(self) -> bool:
        return self.enterable

    def getPortal(self) -> bool:
        return self.portal


class Road(Place):
    def __init__(self, name="道路", info=None):
        super().__init__(name, info)  # 把Place里的__init__搬过来
        if info:
            self.info = f"    {info}"
        else:
            info_list = [
                "       一段整齐的石板路，表面略有凹凸，兴许是岁月冲刷的痕迹...",
                "       一段石板路，有的缝隙中还冒出了几株青苔，生机勃勃...",
                "       一段光滑的石板路，大概是因为来往的行人马车太多，石板的棱角才被磨平吧...",
                "       一段斑驳的石板路，有着坑坑洼洼的印记，或许是...水滴石穿？",
                "       一段零碎的石板路，大大小小的石子夹在石板的缝隙中，还有几株小草从缝隙中钻出...",
                "       一段漂亮的石板路，两侧还有叠瓦装饰，十分好看。",
            ]
            self.info = random.choice(info_list)  # 在以上文本中随机挑选


class Wall(Place):
    def __init__(self, name="石墙", info=None) -> None:
        super().__init__(name, info)
        self.enterable = False  # 石墙不可进入（废话


class Portal(Place):
    def __init__(self, name="传送门", info="传送门") -> None:
        super().__init__(name, info)
        self.portal = True
        self.destination = "hub"  # 默认目的地：hub
        self.destination_pos = [0, 5]  # 传到hub里村长家

    def setDestination(self, destination) -> None:
        self.destination = destination

    def setDestinationPosition(self, position) -> None:
        self.destination_pos = position

    def getDestination(self) -> str:
        return self.destination

    def getDestinationPosition(self) -> list[int]:
        return self.destination_pos


class Shop(Place):
    def __init__(self, name: str = "当铺", info: str = "", goods: dict = None):
        if goods is None:
            goods = {}
        super().__init__(name, info)
        if info:
            self.info = info
        else:
            info_list = ["一家生意兴隆的当铺，人来人往，热闹的很。", "一家不起眼的小当铺，虽不引人注目，但所买商品却是货真价实。"]
            self.info = random.choice(info_list)  # 在以上文本中随机挑选
        self.goods = goods  # {商品名：{"object":商品实例, "amount":数量, "type":类别, "price":价格}}

    def addAGood(self, good: str, object_=None):
        '''
        添加单个商品
        建议同时传入object，Type会同步生成
        '''
        self.goods[good] = {}  # 创建该商品的空字典
        if object_:  # 如果传入了商品对应的object
            self.goods[good]["object"] = object_  # 设置object
            self.goods[good]["type"] = object_.getType()  # 生成Type

    def addGoods(self, goods: list[str], units: list = None):
        '''
        批量添加商品
        建议逐个添加，批量偶尔容易出bug...
        传入Object时，同步生成Type
        '''
        if units is None:
            units = []
        for good in goods:  # 遍历goods
            self.goods[good] = {}  # 一次创建商品对应的空字典
        goods_backup = self.goods  # 备份
        try:
            for i in range(len(goods)):  # 用i作为索引
                self.goods[goods[i]]["object"] = units[i]  # 将商品的object设为units列表中对应的
                self.goods[goods[i]]["type"] = units[i].getType()  # 生成Type
        except IndexError or KeyError as e:  # 若索引超出上限
            self.goods = goods_backup  # 恢复到备份前
            raise KeyError from e

    def setAnObject(self, good, object_):
        '''
        设置商品对应的实例，Type会同步生成
        '''
        self.goods[good]["object"] = object_  # 设置object
        self.goods[good]["type"] = object_.getType()  # 生成Type

    def setUnits(self, goods: list[str], units: list):
        '''
        批量设置实例，Type会同步生成
        '''
        goods_backup = self.goods  # 备份
        try:
            for i in range(len(goods)):  # 用i作为索引
                self.goods[goods[i]]["object"] = units[i]  # 将商品的object设为units列表中对应的
                self.goods[goods[i]]["type"] = units[i].getType()  # 生成Type
        except KeyError as e:  # 若索引超出上限
            self.goods = goods_backup  # 恢复到备份前
            raise KeyError from e

    def setAnAmount(self, good, amount):
        '''
        设置商品对应的价格
        '''
        self.goods[good]["amount"] = amount  # 设置object

    def setAmounts(self, goods: list[str], amount: list[int]):
        '''
        批量设置
        '''
        goods_backup = self.goods  # 备份
        try:
            for i in range(len(goods)):  # 用i作为索引
                self.goods[goods[i]]["amount"] = amount[i]  # 依次设置商品对应的价格
        except KeyError as e:  # 若索引超出上限
            self.goods = goods_backup  # 恢复到备份前
            raise KeyError from e

    def setAType(self, good: str, type_: str):
        '''
        设置商品对应的类型
        '''
        self.goods[good]["type"] = type_

    def setTypes(self, goods: list[str], types: list[str]):
        '''
        批量设置类型
        '''
        goods_backup = self.goods  # 备份
        try:
            for i in range(len(goods)):  # 用i作为索引
                self.goods[goods[i]]["type"] = types[i]  # 依次设置商品对应的type
        except KeyError as e:  # 若索引超出上限
            self.goods = goods_backup  # 恢复到备份前
            raise KeyError from e

    def getAllGoods(self) -> dict[dict, None]:
        # 获取商品列表{商品名：{"object":商品实例, "amount":数量, "type":类别, "price":价格}}
        return self.goods

    def getAllPrices(self) -> dict:
        # 获取所有商品价格
        return {good: self.goods[good]["price"] for good in self.goods}

    def getAllAmounts(self) -> dict:
        # 获取所有商品数量
        return {good: self.goods[good]["amount"] for good in self.goods}

    def getAllTypes(self) -> dict:
        # 获取所有商品类型
        return {good: self.goods[good]["type"] for good in self.goods}

    def getAllGoodsUnits(self) -> dict:
        # 获取所有商品对应实例
        return {good: self.goods[good]["object"] for good in self.goods}

    def getGood(self, good: str = "") -> dict | None:
        # 获取单个商品所对属性 {"object":商品实例, "amount":数量, "type":类别, "price":价格}
        try:
            return self.goods[good]
        except IndexError:
            return None

    def getPrice(self, good: str = "") -> int | None:
        # 获取单个商品所对价格
        try:
            return self.goods[good]["price"]
        except IndexError:
            return None

    def getAmount(self, good: str = "") -> int | None:
        # 获取单个商品所对数量
        try:
            return self.goods[good]["amount"]
        except IndexError:
            return None

    def getType(self, good: str = "") -> str | None:
        # 获取单个商品所对类型
        try:
            return self.goods[good]["type"]
        except IndexError:
            return None

    def getGoodObject(self, good: str = ""):
        # 获取单个商品所对实例
        try:
            return self.goods[good]["object"]
        except IndexError:
            return None


class WorldMap:
    def __init__(self):
        # 示范：（发电警告）
        square = Place(name="中央广场", info="      奇妙的中央大广场，咱也不知道为嘛要叫介个...总之是个测试地点...阿巴")
        hotel = Place(name="客栈", info="      大！！客！！栈！！（突然发电（?")
        restaurant = Shop(
            name="包子铺",
            info="      普通的包子铺，肉香四溢，人来人往。",
            goods={"大包砸": {"amount": 10, "price": 20000}},
        )
        road = Road()
        self.world = [
            ["", "", "", "", restaurant, "", "", "", "", "", "", ""],
            ["", "", "", "", road, "", "", hotel, "", "", "", ""],
            ["", "", "", "", square, road, road, road, "", "", "", ""],
            ["", "", "", "", "", "", "", "", "", "", "", ""],
            ["", "", "", "", "", "", "", "", "", "", "", ""],
        ]
        self.x_size = len(self.world[0])
        self.y_size = len(self.world)

    def getMap(self, pos: list, size: int = None) -> dict[int]:
        """获取地图

        参数
        ----------
            pos (list): 坐标，[x,y]
            size (int): 视距

        返回值
        ----------
            dict[int]:{y1:{x1:place,...},...}
        """
        pos_x = pos[0]
        pos_y = pos[1]
        world_sized = {}
        if not size:
            for y in range(self.y_size):
                world_sized[y] = {}  # 建立这一行
                for x in range(self.x_size):  # 用x遍历，范围为x+size~x-size
                    world_sized[y][x] = self.world[y][x]  # 读取world中的对应坐标
        else:
            for y in range(pos_y - size, pos_y + size + 1):  # 用y遍历，范围为y-size~y+size
                if y < 0 or y > self.y_size - 1:  # 若y超出边界了
                    world_sized[y] = {
                        x: "" for x in range(pos_x - size, pos_x + size + 1)
                    }  # 设置这一行为空
                else:
                    world_sized[y] = {}  # 建立这一行
                    for x in range(
                        pos_x - size, pos_x + size + 1
                    ):  # 用x遍历，范围为x+size~x-size
                        if x < 0 or x > self.x_size - 1:  # 若x超出边界了
                            world_sized[y][x] = ""  # 将这个坐标设置为空
                        else:
                            world_sized[y][x] = self.world[y][x]  # 读取world中的对应坐标

        return world_sized

    def getStringMap(self, pos: list[int], size: int = None):
        """
        将获取的地图转化为字符串

        参数
        ----------
        pos : list[int]
            坐标，[x,y]
        size : int
            视距

        返回值
        -------
        dict[str]
            将原地图内的所有place类替换为其名字
        """
        worldmap = self.getMap(pos, size)
        string = {}
        for row in list(worldmap.keys()):  # 提取行号：[y1, y2, y3, y4...]
            string[row] = {}  # 设置这一行为{}
            for col in list(list(worldmap.values())[0].keys()):
                '''
                list(worldmap.values()) : [{x1:...,x2:...,...},{...}]
                list(worldmap.values())[0] : {x1:...,x2:...,...}
                list(list(worldmap.values())[0].keys()) : [x1,x2,...]
                '''
                string[row][col] = worldmap[row][col].__str__()
        return string

    def getCurrPlace(self, pos: list) -> str:
        '''
        获取当前地点
        '''
        # 此处pos为[x,y]
        # 而world列表中索引方式为world[y][x]
        return self.world[pos[1]][pos[0]]  # y,x

    def getCurrUnits(self, pos: list[int]):
        """
        获取当前地点单位
        """
        curr_place = self.world[pos[1]][pos[0]]
        return curr_place.getUnits()

    def getCurrStrUnits(self, pos: list[int]):
        """
        返回形式：[名称,...]
        """
        curr_units = self.getCurrUnits(pos)
        return {unit.__str__(): unit.getAmount() for unit in curr_units}

    def getCurrDictUnits(self, pos):
        """
        返回形式：{名称:实例,...}
        """
        curr_units = self.getCurrUnits(pos)
        return {unit.__str__(): unit for unit in curr_units}

    def getNorth(self, pos: list) -> Place | None | None:
        '''
        获取北方单位（x,y-1）
        '''
        if pos[1] == 0:
            return None
        place = self.world[pos[1] - 1][pos[0]]
        if place != "" and place.getEnterable() == True:  # 若该场景不为空且可进入
            return place  # 返回该场景实例
        else:
            return None

    def getSouth(self, pos: list) -> Place | None:
        '''
        获取南方单位（x,y+1）
        '''
        if pos[1] == self.y_size - 1:
            return None
        place = self.world[pos[1] + 1][pos[0]]
        if place != "" and place.getEnterable() == True:  # 若该场景不为空且可进入
            return place  # 返回该场景实例
        else:
            return None

    def getWest(self, pos: list) -> Place | None:
        '''
        获取西方单位（x-1,y）
        '''
        if pos[0] == 0:
            return None
        place = self.world[pos[1]][pos[0] - 1]
        if place != "" and place.getEnterable() == True:  # 若该场景不为空且可进入
            return place  # 返回该场景实例
        else:
            return None

    def getEast(self, pos: list) -> Place | None:
        '''
        获取东方单位（x+1,y）
        '''
        if pos[0] == self.x_size - 1:
            return None
        place = self.world[pos[1]][pos[0] + 1]
        if place != "" and place.getEnterable() == True:  # 若该场景不为空且可进入
            return place  # 返回该场景实例
        else:
            return None

    def getNorthWest(self, pos: list) -> Place | None:
        '''
        获取西北方向单位（x-1,y-1）
        '''
        if pos[0] == 0 or pos[1] == 0:
            return None
        place = self.world[pos[1] - 1][pos[0] - 1]  # y-1,x-1
        if place != "" and place.getEnterable() == True:  # 若该场景不为空且可进入
            return place  # 返回该场景实例
        else:
            return None

    def getNorthEast(self, pos: list) -> Place | None:
        '''
        获取东北方向单位（x+1,y-1）
        '''
        if pos[0] == self.x_size - 1 or pos[1] == 0:
            return None
        place = self.world[pos[1] - 1][pos[0] + 1]  # y-1,x+1
        if place != "" and place.getEnterable() == True:  # 若该场景不为空且可进入
            return place  # 返回该场景实例
        else:
            return None

    def getSouthEast(self, pos: list) -> Place | None:
        '''
        获取东南方向单位（x+1,y+1）
        '''
        if pos[0] == self.x_size - 1 or pos[1] == self.y_size - 1:
            return None
        place = self.world[pos[1] + 1][pos[0] + 1]  # y+1,x+1
        if place != "" and place.getEnterable() == True:  # 若该场景不为空且可进入
            return place  # 返回该场景实例
        else:
            return None

    def getSouthWest(self, pos: list) -> Place | None:
        '''
        获取西南方向单位（x-1,y+1）
        '''
        if pos[0] == 0 or pos[1] == self.y_size - 1:
            return None
        place = self.world[pos[1] + 1][pos[0] - 1]  # y+1,x-1
        if place != "" and place.getEnterable() == True:  # 若该场景不为空且可进入
            return place  # 返回该场景实例
        else:
            return None

    def getExitUnits(self, pos: list) -> dict[str, Place | None]:
        '''
        获取出口单位
        返回List[str|None]
        '''
        north = self.getNorth(pos)
        south = self.getSouth(pos)
        east = self.getEast(pos)
        west = self.getWest(pos)
        north_east = self.getNorthEast(pos)
        north_west = self.getNorthWest(pos)
        south_east = self.getSouthEast(pos)
        south_west = self.getSouthWest(pos)
        return {
            "N": north,
            "S": south,
            "E": east,
            "W": west,
            "NE": north_east,
            "NW": north_west,
            "SE": south_east,
            "SW": south_west,
        }

    def getStrExitUnits(self, pos: list[int]):
        # 将出口对象转换为字符串
        exit_units = self.getExitUnits(pos)
        return {direction: exit_units[direction].__str__() for direction in exit_units}

    def getIfExit(self, pos: list) -> dict:
        '''
        获取出口
        返回字典：{方向:True/False,...}
        '''
        if_exit = {}
        north = self.getNorth(pos)
        south = self.getSouth(pos)
        east = self.getEast(pos)
        west = self.getWest(pos)
        north_east = self.getNorthEast(pos)
        north_west = self.getNorthWest(pos)
        south_east = self.getSouthEast(pos)
        south_west = self.getSouthWest(pos)
        exits = {
            "N": north,
            "S": south,
            "E": east,
            "W": west,
            "NE": north_east,
            "NW": north_west,
            "SE": south_east,
            "SW": south_west,
        }
        for direction in exits:
            if exits[direction]:
                if_exit[direction] = True
            else:
                if_exit[direction] = False
        return if_exit

    def moveTo(self, pos: list[int], direction: str):
        '''
        向[direction]方向移动
        '''
        if_exit = self.getIfExit(pos)
        if not direction or not if_exit[direction]:
            raise MapEdgeError(pos)
        if direction == "E":
            return [pos[0] + 1, pos[1]]
        elif direction == "N":
            return [pos[0], pos[1] - 1]
        elif direction == "NE":
            return [pos[0] + 1, pos[1] - 1]
        elif direction == "NW":
            return [pos[0] - 1, pos[1] - 1]
        elif direction == "S":
            return [pos[0], pos[1] + 1]
        elif direction == "SE":
            return [pos[0] + 1, pos[1] + 1]
        elif direction == "SW":
            return [pos[0] - 1, pos[1] + 1]
        elif direction == "W":
            return [pos[0] - 1, pos[1]]
        else:
            raise DirectionError(direction)

    def formatWorldMap(self, ifAxis: bool = True) -> PrettyTable:
        '''
        格式化地图
        '''
        x_size = len(self.world[0])  # 横宽
        y_size = len(self.world)  # 竖高

        worldMap_talbe = PrettyTable()  # 创建prettytable
        worldMap_talbe.vertical_char = " "  # 设置竖直线段为空
        worldMap_talbe.junction_char = "—"  # 设置连接符为=
        worldMap_talbe.horizontal_char = "—"

        if not ifAxis:  # 若ifAxis为False（是否输出坐标轴）
            worldMap_talbe.header = False
            table_header = [str(i) for i in range(x_size)]
            worldMap_talbe.field_names = table_header

            for i in range(y_size):  # 重复行数次
                world_map_row = self.world[i]  # 提取每行
                worldMap_talbe.add_row(world_map_row)  # 添加到table中

        else:  # 打印坐标轴（即在左侧添加一列数字，用insert插入到world_map中）
            worldMap_talbe.header = True
            table_header = ["坐标|"]  # 表头新增一列供坐标显示

            table_header.extend(str(i) for i in range(x_size))
            worldMap_talbe.field_names = table_header

            for i in range(y_size):
                world_map_row = self.world[i]
                index = ["{i}  |".format(i=i)]  # 在地图的第一列新增一个序号
                worldMap_talbe.add_row(index + world_map_row)

        return worldMap_talbe


if __name__ == "__main__":
    worldMap = WorldMap()
    pos = [4, 2]
    print(worldMap.formatWorldMap(ifAxis=True))  # 输出地图
    pos = worldMap.moveTo(pos, "E")
    print(worldMap.getCurrPlace(pos))  # 获取当前位置
    print(worldMap.getCurrPlace(pos).getInfo())  # 获取当前位置的信息
    pos = worldMap.moveTo(pos, "E")
    print(worldMap.getCurrPlace(pos))  # 获取当前位置
    print(worldMap.getCurrPlace(pos).getInfo())  # 获取当前位置的信息
